package optimizers.ganeat.math;

import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;

import javax.swing.JPanel;
import javax.swing.JTabbedPane;

import org.math.plot.utils.Array;

import common.Static;

import bsh.EvalError;
import bsh.Interpreter;
import optimizers.commons.RunConfig;
import optimizers.ganeat.amplify.AmplifyIndividual;
import util.MyUtil;
import visualization.PlotExpData;
import model.Constants;
import model.OligoSystemGeneral;
import fitness.MathFitness;
import fitness.MathResult;

public class IndividualMath extends AmplifyIndividual {

	public IndividualMath(Map<String, Integer> innovations,
			Map<String, String> nodeNames, int geneIndex, int indivIndex,
			RunConfig config) {
		super(innovations, nodeNames, geneIndex, indivIndex, config);
		// TODO Auto-generated constructor stub
	}

	public IndividualMath(Map<String, Integer> innovations,
			Map<String, String> nodeNames, int geneIndex, int indivIndex,
			RunConfig config, String string) {
		super(innovations, nodeNames, geneIndex, indivIndex, config, string);
		// TODO Auto-generated constructor stub
	}

	@Override
	public JPanel displayTimeSeriesWithButton() {
		JPanel panel = new JPanel();
		ArrayList<double[]> tests = ConstantsMath.logScale ? MyUtil
				.getTestsForMathLogScale(ConstantsMath.minDisplay,
						ConstantsMath.maxDisplay, ConstantsMath.nDisplays)
				: MyUtil.getTestsForMath(ConstantsMath.minDisplay,
						ConstantsMath.maxDisplay, ConstantsMath.nDisplays);
		MathResult fitness = (MathResult) this.fitnessResult;
		double maxY = 0;
		if (fitness != null) {
			double[][] fitData = new double[ConstantsMath.y_scaling ? 3 : 2][tests
					.size()];
			for (int k = 0; k < tests.size(); k++) {
				double[] inputs = tests.get(k);
				MathResult result = (MathResult) this.fitnessResult;
				double target = result.targets.get(Array.toString(inputs));
				fitData[0][k] = target;
				double actual = result.actuals.get(Array.toString(inputs));
				fitData[1][k] = actual;
			}

			double[] xData = new double[tests.size()];
			for (int i = 0; i < xData.length; i++) {
				xData[i] = tests.get(i)[0];
			}

			if (ConstantsMath.nInput == 1) {
				final PlotExpData demo1 = new PlotExpData("Generation "
						+ this.generationIndex + " Index "
						+ this.individualIndex, fitData, xData,
						ConstantsMath.y_scaling ? new String[] { "Target",
								"Actual", "Scaled Actual" } : new String[] {
								"Target", "Actual" }, "chart", "Input (nM)",
						"Output (nM)", true, maxY * 1.2, 10);
				panel.add(demo1);
			} else if (ConstantsMath.nInput == 2) {
				double[][] fitData1 = new double[fitData.length
						* ConstantsMath.nDisplays][ConstantsMath.nDisplays];
				double[] xData1 = new double[ConstantsMath.nDisplays];
				String[] seriesName = new String[ConstantsMath.nDisplays
						* fitData.length];
				for (int i = 0; i < fitData.length; i++) {
					for (int j = 0; j < ConstantsMath.nDisplays; j++) {
						for (int k = 0; k < ConstantsMath.nDisplays; k++) {
							fitData1[j * fitData.length + i][k] = fitData[i][j
									* ConstantsMath.nDisplays + k];
						}
					}
				}
				for (int i = 0; i < ConstantsMath.nDisplays; i++) {
					xData1[i] = tests.get(i)[1];
					seriesName[i * fitData.length] = "y="
							+ Static.df2.format(xData1[i]) + "(Target)";
					seriesName[i * fitData.length + 1] = "y="
							+ Static.df2.format(xData1[i]) + "(Actual)";
					if (fitData.length == 3) {
						seriesName[i * ConstantsMath.nDisplays + 2] = "y="
								+ Static.df2.format(xData1[i]) + "(Scaled)";
					}
				}
				final PlotExpData demo1 = new PlotExpData("Generation "
						+ this.generationIndex + " Index "
						+ this.individualIndex, fitData1, xData1, seriesName,
						"chart", "Input (nM)", "Output (nM)", true, maxY * 1.2,
						10);
				panel.add(demo1);
			} else {
				final PlotExpData demo1 = new PlotExpData("Generation "
						+ this.generationIndex + " Index "
						+ this.individualIndex, fitData,
						ConstantsMath.y_scaling ? new String[] { "Target",
								"Actual", "Scaled Actual" } : new String[] {
								"Target", "Actual" }, "Input (set number)",
						"Output (nM)");
				panel.add(demo1);
			}
		}
		return panel;
	}

	public JPanel displayTimeSeriesWithButton1() {
		JPanel panel = new JPanel();
		panel.setLayout(new GridLayout(2, 1));
		OligoSystemGeneral oligoModel = this.getOligoModel();
		boolean[] sequenceProtected = new boolean[oligoModel.nSimpleSequences];
		for (int i = 0; i < ConstantsMath.nInput; i++) {
			sequenceProtected[i] = true;
		}
		for (int i = ConstantsMath.nInput; i < oligoModel.nSimpleSequences; i++) {
			sequenceProtected[i] = false;
		}
		oligoModel.sequenceProtected = sequenceProtected;
		double[] reporterConcentration = new double[oligoModel.nSimpleSequences];
		for (int i = 0; i < oligoModel.nSimpleSequences; i++) {
			reporterConcentration[i] = 0;
		}
		reporterConcentration[ConstantsMath.nInput] = ConstantsMath.reporterConcentration;
		oligoModel.reporterConcentration = reporterConcentration;
		JTabbedPane tabbedPane = new JTabbedPane();
		panel.add(tabbedPane);

		ArrayList<double[]> tests = ConstantsMath.logScale ? MyUtil
				.getTestsForMathLogScale(ConstantsMath.minDisplay,
						ConstantsMath.maxDisplay, ConstantsMath.nDisplays)
				: MyUtil.getTestsForMath(ConstantsMath.minDisplay,
						ConstantsMath.maxDisplay, ConstantsMath.nDisplays);

		for (int k = 0; k < tests.size(); k++) {
			double[] inputs = tests.get(k);
			for (int i = 0; i < ConstantsMath.nInput; i++) {
				oligoModel.seqConcentration[0][i] = inputs[i];
			}
			double[][] data = oligoModel.getTimeSeriesComplexModelTillStable();
			String[] seqNames = new String[data.length];
			String outputName = oligoModel.intToName.get(ConstantsMath.nInput);
			for (int j = 0; j < data.length; j++) {
				if (j == data.length - 1) {
					seqNames[j] = outputName + "(r)";
				} else if (j >= ConstantsMath.nInput * 2) {
					seqNames[j] = oligoModel.intToName.get(j
							- ConstantsMath.nInput);
				} else {
					seqNames[j] = j % 2 == 0 ? oligoModel.intToName.get(j / 2)
							+ "'" : oligoModel.intToName.get(j / 2);
				}
				if (seqNames[j] == null)
					seqNames[j] = String.valueOf(j);
			}
			final PlotExpData demo = new PlotExpData("Generation "
					+ this.generationIndex + " Index " + this.individualIndex,
					data, seqNames, "Time", "Expression", 0, 1, true, 0, 0);
			tabbedPane.add(demo, k + ":" + MyUtil.arrayToString(inputs));
		}

		MathResult fitness = (MathResult) this.fitnessResult;
		double maxY = 0;
		if (fitness != null) {
			double[][] fitData = new double[ConstantsMath.y_scaling ? 3 : 2][tests
					.size()];
			for (int k = 0; k < tests.size(); k++) {
				double[] inputs = tests.get(k);
				StringBuilder builder = new StringBuilder();
				builder.append(ConstantsMath.preExpression);
				String expression = ConstantsMath.expression;
				for (int i = 0; i < ConstantsMath.nInput; i++) {
					expression = expression.replace("input" + i,
							String.valueOf(inputs[i]));
				}
				builder.append(expression);
				Interpreter interpreter = new Interpreter();
				try {
					interpreter.eval(builder.toString());
					Double r = (Double) interpreter.get("result");
					fitData[0][k] = r;
					if (maxY < r)
						maxY = r;
				} catch (EvalError e) {
					e.printStackTrace();
					fitData[0][k] = 0;
				}

				for (int i = 0; i < ConstantsMath.nInput; i++) {
					oligoModel.seqConcentration[0][i] = inputs[i];
				}
				double[][] data1 = oligoModel
						.getTimeSeriesComplexModelTillStable();
				int output = data1.length - 1;
				int firstStableTime = data1[output].length - 1;
				while (data1[output][firstStableTime] == 0) {
					firstStableTime--;
				}
				fitData[1][k] = data1[output][firstStableTime];
				if (ConstantsMath.y_scaling) {
					fitData[2][k] = data1[output][firstStableTime]
							* fitness.scale;
				}
			}

			double[] xData = new double[tests.size()];
			for (int i = 0; i < xData.length; i++) {
				xData[i] = tests.get(i)[0];
			}

			if (ConstantsMath.nInput == 1) {
				final PlotExpData demo1 = new PlotExpData("Generation "
						+ this.generationIndex + " Index "
						+ this.individualIndex, fitData, xData,
						ConstantsMath.y_scaling ? new String[] { "Target",
								"Actual", "Scaled Actual" } : new String[] {
								"Target", "Actual" }, "chart", "Input (nM)",
						"Output (nM)", true, maxY * 1.2, 10);
				panel.add(demo1);
			} else if (ConstantsMath.nInput == 2) {
				double[][] fitData1 = new double[fitData.length
						* ConstantsMath.nDisplays][ConstantsMath.nDisplays];
				double[] xData1 = new double[ConstantsMath.nDisplays];
				String[] seriesName = new String[ConstantsMath.nDisplays
						* fitData.length];
				for (int i = 0; i < fitData.length; i++) {
					for (int j = 0; j < ConstantsMath.nDisplays; j++) {
						for (int k = 0; k < ConstantsMath.nDisplays; k++) {
							fitData1[j * fitData.length + i][k] = fitData[i][j
									* ConstantsMath.nDisplays + k];
						}
					}
				}
				for (int i = 0; i < ConstantsMath.nDisplays; i++) {
					xData1[i] = tests.get(i)[1];
					seriesName[i * fitData.length] = "y="
							+ Static.df2.format(xData1[i]) + "(Target)";
					seriesName[i * fitData.length + 1] = "y="
							+ Static.df2.format(xData1[i]) + "(Actual)";
					if (fitData.length == 3) {
						seriesName[i * ConstantsMath.nDisplays + 2] = "y="
								+ Static.df2.format(xData1[i]) + "(Scaled)";
					}
				}
				final PlotExpData demo1 = new PlotExpData("Generation "
						+ this.generationIndex + " Index "
						+ this.individualIndex, fitData1, xData1, seriesName,
						"chart", "Input (nM)", "Output (nM)", true, maxY * 1.2,
						10);
				panel.add(demo1);
			} else {
				final PlotExpData demo1 = new PlotExpData("Generation "
						+ this.generationIndex + " Index "
						+ this.individualIndex, fitData,
						ConstantsMath.y_scaling ? new String[] { "Target",
								"Actual", "Scaled Actual" } : new String[] {
								"Target", "Actual" }, "Input (set number)",
						"Output (nM)");
				panel.add(demo1);
			}
		}
		return panel;
	}

	/**
	 * 
	 */
	private static final long serialVersionUID = 5215001072434783056L;

}